#pragma once

#include <assert.h>
#include <string>
#include <glib.h>
#include <glibmm.h>

#include "../Shared/GObjHelpers.hpp"
#include "../Shared/OutStream.hpp"
#include "PreProcException.hpp"

namespace CPreProc
{
  enum TokenType
  {
    TokenType_Keyword,
    TokenType_Identifier,
    TokenType_Constant,
    TokenType_StringLiteral,
    TokenType_Punctuator,
    TokenType_HeaderName,
    TokenType_PPNumber,
    TokenType_CharacterConstant,
    TokenType_WhiteSpace,

    TokenType_PlacemakerPPToken,    // used by the ## operator. Specified in the C standard, 6.10.3.3
    TokenType_Misc,
  };

  enum Keyword
  {
    Keyword_Auto      = 1000,
    Keyword_Break,
    Keyword_Case,
    Keyword_Char,
    Keyword_Const,
    Keyword_Continue,
    Keyword_Default,
    Keyword_Do,
    Keyword_Double,
    Keyword_Else,
    Keyword_Enum,
    Keyword_Extern,
    Keyword_Float,
    Keyword_For,
    Keyword_Goto,
    Keyword_If,
    Keyword_Inline,
    Keyword_Int,
    Keyword_Long,
    Keyword_Register,
    Keyword_Restrict,
    Keyword_Return,
    Keyword_Short,
    Keyword_Signed,
    Keyword_SizeOf,
    Keyword_Static,
    Keyword_Struct,
    Keyword_switch,
    Keyword_TypeDef,
    Keyword_Union,
    Keyword_Unsigned,
    Keyword_Void,
    Keyword_Volatile,
    Keyword_While,
    Keyword_Bool,
    Keyword_Complex,
    Keyword_Imaginary,
  };

  enum Punctuator
  {
    Punct_None                = 0,

    Punct_LeftSquareBracket   = 2000,
    Punct_RightSquareBracket,
    Punct_LeftParenthesis,
    Punct_RightParenthesis,
    Punct_LeftCurlyBracket,
    Punct_RightCurlyBracket,
    Punct_Dot,
    Punct_PtrMember,
    Punct_Inc,
    Punct_Dec,
    Punct_Amp,
    Punct_Star,
    Punct_Plus,
    Punct_Minus,
    Punct_Invert,
    Punct_Not,
    Punct_Div,
    Punct_Modulo,
    Punct_ShiftLeft,
    Punct_ShiftRight,
    Punct_LessThan,
    Punct_GreaterThan,
    Punct_LessOrEqual,
    Punct_GreaterOrEqual,
    Punct_Equal,
    Punct_Unequal,
    Punct_BitXor,
    Punct_BitOr,
    Punct_LogAnd,
    Punct_LogOr,
    Punct_QuestionMark,
    Punct_Colon,
    Punct_SemiColon,
    Punct_ThreeDots,
    Punct_Assign,
    Punct_MulAndAssign,
    Punct_DivAndAssign,
    Punct_ModAndAssign,
    Punct_AddAndAssign,
    Punct_SubAndAssign,
    Punct_ShiftLeftAndAssign,
    Punct_ShiftRightAndAssign,
    Punct_BitAndAndAssign,
    Punct_BitXorAndAssign,
    Punct_BitOrAndAssign,
    Punct_Comma,
    Punct_Sharp,
    Punct_DoubleSharp,
  };

  enum StringLiteralType
  {
    StringLiteralType_CharString = 3000,
    StringLiteralType_WideString,
  };

  enum HeaderNameType
  {
    HeaderNameType_Quoted           = 4000,
    HeaderNameType_AngularBrackets,
  };

  class TokenDef;
  class KeywordDef;
  class PunctuatorDef;
  class StringLiteralDef;
  class IdentifierTokenDef;
  class PPNumberTokenDef;
  class WhitespaceTokenDef;
  
  class Token
  {
    friend class Tokenizer;
    friend class PunctuatorDef;
    friend class StringLiteralDef;
    friend class IdentifierTokenDef;
    friend class PPNumberTokenDef;
    friend class WhitespaceTokenDef;
    
  public:
    Token(){}
    Token(const gchar *begin,const gchar *end,guint16 type,guint16 token_id)
      : begin(begin),
      end(end),
      type(type),
      token_id(token_id)
    {
    }

    int get_token_id() const { return token_id; }
    TokenType get_token_type() const { return (TokenType)type; }

    Glib::ustring     to_string() const;
    bool operator == (const gchar *b) const;
    bool operator == (const Glib::ustring &b) const;

    bool operator != (const gchar *b) const { return !(*this == b); }
    bool operator != (const Glib::ustring &b) const { return !(*this == b); }

    const gchar*      get_begin() const { return begin; }
    const gchar*      get_end() const { return end; }

  private:
    const gchar*      begin;
    const gchar*      end;

    guint16           type;
    guint16           token_id;

    static Token      create_constant_token(const char *str,guint16 type,guint16 token_id);

  public:

    // a bunch of constant tokens
    static const Token zero_pp_number;
    static const Token one_pp_number;
    static const Token single_space_whitespace;
    static const Token placemaker_pp_token;
  };

  class Tokenizer
  {
    friend class KeywordDef;
    friend class PunctuatorDef;
    friend class StringLiteralDef;
    friend class IdentifierTokenDef;
    friend class PPNumberTokenDef;
    friend class WhitespaceTokenDef;

  public:
    static void init();
    static void shutdown();

    Tokenizer();
    Tokenizer(const gchar *begin,const gchar *end);
    Tokenizer(const gchar *null_terminated);

    gchar cur() const
    {
      assert(cur_char < end_char);
      return *cur_char;
    }

    bool end_of_data() const
    {
      return cur_char == end_char;
    }

    void advance()
    {
      assert(cur_char != end_char);
      cur_char++;
    }

    void move_to_end()
    {
      cur_char = end_char;
    }

    gunichar cur_utf8() const;

    const gchar* get_cur_ptr() const
    {
      return cur_char;
    }

    // advances in an utf8 aware manner. If two or more bytes form one utf8 char, all of them are skipped at once.
    // under most circumstance (ie whenever any extended char would be an error), using just advance should be fine.
    // of course, you can mix the usage of advance and advance_utf8. 
    void advance_utf8();

    bool          skip_optional_whitespace();
    void          skip_required_whitespace();

    bool          compare_advance_on_equal(const gchar *b);

    Token         read_pp_token();
    Token         read_identifier();
    bool          try_read_header_name(Token &out);

    Token         read_to_end();

  private:
    const gchar*      cur_char;
    const gchar*      end_char;

    static void       utf8_compute(gchar initial_char, int &mask, int &len);

    TokenDef*         find_token_def();

    static bool       inited;
    static TokenDef*  pp_token_def_table[];
    static void       add_token_def(TokenDef *token_def);

    static void       init_punctuator_token_defs();
    static void       init_string_literal_token_defs();
    static void       init_identifier_token_defs();
    static void       init_pp_number_token_defs();
    static void       init_whitespace_defs();
  };

  // see C standard paragraph 6.4.3
  static inline bool is_identifier_first_char(gunichar c)
  {
    return (c >= 'a' && c <= 'z') ||
      (c >= 'A' && c <= 'Z') ||
      c == '_' ||
      (c >= 0xa0 && c < 0xd800)||
      c > 0xdfff;
  }

  static inline bool is_identifier_char(gunichar c)
  {
    return is_identifier_first_char(c) || 
      c >= '0' && c <= '9';
  }

  static inline bool is_whitespace(gchar c)
  {
    return c == ' ' || c == '\t' || c == '\n' || c == '\r';
  }

  bool validate_identifier(const gchar *begin,const gchar *end = 0);
  static inline bool validate_identifier(const Glib::ustring &str)
  {
    return validate_identifier(str.c_str(),str.c_str() + str.bytes());
  }

  static inline bool is_digit(gchar c)
  {
    return c >= '0' && c <= '9';
  }

  //////////////
  // Tokenizer exception
  class TokenizerException : public PreProcException
  {
  public:
    TokenizerException(const Glib::ustring &msg)
      : PreProcException(msg)
    {
    }

  protected:
    TokenizerException(){}
  };

  class TokenizerInvalidUTF8Exception : TokenizerException
  {
  public:
    virtual Glib::ustring get_message() const
    {
      return "Corrupted UTF8 data.";
    }
  };
}
